package com.webserver.http;

import com.webserver.core.EmptyRequestException;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 请求对象
 * 该类的每一个实例用于表示HTTP协议规定的一个请求内容。
 * 每个请求由三部分构成:
 * 请求行，消息头，消息正文
 */
public class HttpRequest {
    private String charsetName="UTF-8";//默认字符即UTF-8,用于获取参数转码使用

    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    private String requestURI;//uri中?左侧的请求部分
    private String queryString;//uri中?右侧的参数部分
    /*
        保存每一组客户端提交的参数
        key:参数名   value:参数值
     */
    private Map<String, String> parameters = new HashMap<>();


    //消息头相关信息
    private Map<String, String> headers = new HashMap<>();

    //消息正文相关信息
    private byte[] contentData;

    private Socket socket;

    public HttpRequest(Socket socket) throws IOException, EmptyRequestException {
        this.socket = socket;
        //1.1读取请求行
        parseRequestLine();
        //1.2解析消息头
        parseHeaders();
        //1.3解析消息正文
        parseContent();
    }

    private void parseRequestLine() throws IOException, EmptyRequestException {
        String line = readLine();
        if (line.isEmpty()) {//如果请求行为空字符串，则认定为空请求
            throw new EmptyRequestException();
        }
        System.out.println(line);

        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];//这里后期会出现数组下标越界异常，这是由于空请求造成的，后期解决
        protocol = data[2];

        parseUri();//进一步解析uri

        System.out.println("method:" + method);//GET
        System.out.println("uri:" + uri);// /index.html
        System.out.println("protocol:" + protocol);//HTTP/1.1
    }

    private void parseUri() {
        /*
            uri存在两种情况:有参数和无参数
            无参数时:
                如:/myweb/index.html
                只需要直接将uri的值赋值给requestURI即可。
                queryString和parameters不需要做任何操作。
            有参数时:
                如:/myweb/regUser?username=xxx&password=xxx&...
                需要做如下操作:
                1:将uri按照"?"分隔为请求和参数部分，并将请求部分赋值给requestURI，将参数部分
                赋值给queryString
                2:进一步拆分参数部分，首先将参数部分按照"&"拆分出每一组参数。然后每组参数再按照
                "="拆分出参数名和参数值，并将参数名作为key，参数值作为value存入parameters这个
                Map中即可。
            注:上述提及的:requestURI,queryString,parameters为HttpRequest中定义的属性。
         */
        /*
            /myweb/index.html
            /myweb/regUser?username=&password=xxx&...
         */
        String[] data = uri.split("\\?");
        requestURI = data[0];
        if (data.length > 1) {
            queryString = data[1];
            parseParameters(queryString);
        }
        System.out.println("requestURI:" + requestURI);
        System.out.println("queryString:" + queryString);


    }

    private void parseParameters(String line) {
        String[] paras = line.split("&");//拆分出每一组参数
        for (String para : paras) {//para:username=xxxxx
            String[] arr = para.split("=");
            if (arr.length > 1) {
                parameters.put(arr[0], arr[1]);
            } else {
                parameters.put(arr[0], null);
            }
        }
        System.out.println("parameters:" + parameters);
    }

    private void parseHeaders() throws IOException {
        while (true) {
            String line = readLine();
            if (line.isEmpty()) {//单独读取到了CRLF
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0].toLowerCase(), data[1]);
        }
        System.out.println("headers:" + headers);
    }

    private void parseContent() throws IOException {
        //先判断本次请求方式是否为post请求
        if ("post".equalsIgnoreCase(method)) {
            //查看是否存在消息头Content-Length
            if (headers.containsKey("content-length")) {
                int length = Integer.parseInt(headers.get("content-length"));
                //读取正文数据
                InputStream in = socket.getInputStream();
                contentData = new byte[length];
                in.read(contentData);
                //将正文数据读入字节数组

                //获取正文的类型来进行对应处理
                String type = headers.get("content-type");
                if ("application/x-www-form-urlencoded".equals(type)) {
                    //这个正文是个表单数据，原GET请求提交时URL中?右侧的内容
                    String line = new String(contentData);
                    System.out.println("正文内容：" + line);
                    //将参数进行拆分并存入parameters保存
                    parseParameters(line);
                }
            }
        }
    }

    private String readLine() throws IOException {
        /*
            当socket对象是同一个时，无论调用多少次它的getInputStream()方法获取的输入流始终是
            同一个输入流。输出流也是一样的。
         */
        InputStream in = socket.getInputStream();
        int d;
        StringBuilder builder = new StringBuilder();
        char pre = 'a', cur = 'a';//pre表示上次读取的字符，cur表示本次读取到的字符
        while ((d = in.read()) != -1) {
            cur = (char) d;
            if (pre == 13 && cur == 10) {
                break;
            }
            builder.append(cur);
            pre = cur;
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    public String getHeader(String name) {
        return headers.get(name);
    }

    public String getRequestURI() {
        return requestURI;
    }

    public String getQueryString() {
        return queryString;
    }

    /**
     * 获取给定的参数对应的值
     *
     * @param name
     * @return
     */
    public String getParameter(String name) {
        String value=parameters.get(name);
        if (value!=null){
            try {
                value= URLDecoder.decode(value,charsetName);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

        }
        return value;
    }

    /**
     * 设置字符集
     * @param charsetName
     */
    public void setCharsetName(String charsetName) {
        this.charsetName = charsetName;
    }
}
